1. Le patron Observer

observer

1.1. Motivation

observer illustration
Figure 1. L’illustration classique du patron Observer

1.2. Définition

Observateur définit une relation entre objets de type un-à-plusieurs, de façon que, lorsqu’un objet change d’état, tous ceux qui en dépendent en soient notifiés et soient mis à jour automatiquement.

observateur
Figure 2. Modèle UML du patron Observer

1.3. Application

Le patron Observer est utilisable dans de nombreuses situations :

  • Quand un concept a deux aspects, l’un dépendant de l’autre. Encapsuler ces aspects dans des objets séparés permet de les utiliser et les laisser évoluer de manière indépendante.

  • Dès que le changement d’un objet entraîne le changement de plusieurs autres.

  • Dès qu’un objet doit en notifier un certain nombre d’autres sans les connaitre.

1.4. Observer en Java

Java fournit des classes Observable/Observer pour le patron Observer. La classe java.util.Observable est la classe de base pour les sujets. Ainsi, toute classe qui veut être observée étant cette classe dont voici les caractéristiques :

  • fournit des méthodes pour ajouter/enlever des observateurs

  • fournit des méthodes pour notifier les observateurs

  • une sous-classe concrète doit seulement s’occuper de notifier à chque méthode modifiant l’état des objets (mutators)

  • utilise un vecteur stoquant les références des observateurs

L’interface java.util.Observer correspond aux observateurs qui doivent implémenter cette interface.

1.4.1. La classe java.util.Observable

Voici la liste des méthodes de java.util.Observable :

1 public Observable()
2 public synchronized void addObserver(Observer o)
3 protected synchronized void setChanged()
4 public synchronized void deleteObserver(Observer o)
5 protected synchronized void clearChanged()
6 public synchronized boolean hasChanged()
7 public void notifyObservers(Object arg)
8 public void notifyObservers()

1.4.2. L’interface java.util.Observer

java.util.Observer
 1 /**
 2 * This method is called whenever the observed object is changed. An
 3 * application calls an observable object's notifyObservers method to have all
 4 * the object's observers notified of the change.
 5 *
 6 * Parameters:
 7 * o - the observable object
 8 * arg - an argument passed to the notifyObservers method
 9 */
10 public abstract void update(Observable o, Object arg)

2. Une implémentation du MVC : les JTable java

JTable
Figure 3. Ceci n’est pas une JTable

2.2. L’architecture

JTable
Figure 4. Architecture de JTable

3. Le patron Adaptateur

adaptateur

3.1. Le problème

On veut pouvoir :

  • utiliser une classe existante, mais dont l’interface ne coïncide pas avec celle escomptée.

  • créer une classe réutilisable qui collabore avec des classes sans relations avec elle et encore inconnues, c’est-à-dire avec des classes qui n’auront pas nécessairement des interfaces compatibles.

  • vous avez besoin d’utiliser plusieurs sous-classes existantes, mais l'adaptation de leur interface par dérivation de chacune d’entre elles est impraticable. Un adaptateur objet peut adapter l’interface de sa classe parente.

    Note Ce dernier cas ne concerne que le cas "adaptateur d’objet"

3.2. Exemple concret : le retour des canards

  • L’existant :

public interface Canard {
  public void cancaner();
  public void voler();
}

public class Colvert implements Canard {
  public void cancaner() {
    System.out.println("Coincoin");
  }
  public void voler() {
    System.out.println("Je vole");
  }
}
  • Le "presque canard" :

public interface Dindon {
  public void glouglouter();
  public void voler();
}

public class DindonSauvage implements Dindon {
  public void glouglouter() {
    System.out.println("Glouglou");
  }
  public void voler() {
    System.out.println("Je ne vole pas loin");
  }
}

Vous êtes à court d’objets Canard et vous aimeriez utiliser des objets Dindon à la place!

public class AdaptateurDindon implements Canard {
  Dindon dindon;

  ...

  public void cancaner() {
    dindon.glouglouter();
  }

  public void voler() {
    // Adaptation du vol
    for(int i=0; i < 5; i++) {
      dindon.voler();
    }
  }
}

3.3. Le patron Adaptateur

Adaptateur (Adaptor) permet de convertir l’interface d’une classe en une autre conformément à l’attente du client. L’Adaptateur permet à des classes de collaborer, alors qu’elles n’auraient pas pu le faire du fait d’interfaces incompatibles.

adapteur
Figure 5. Modèle UML du patron Adaptateur
adaptor google
Figure 6. Adaptateur sur Google

4. Le patron Visiteur

les visiteurs

4.1. Le problème

Quelques situations à problème :

  • Une structure d’objets contient beaucoup de classes différentes d’interfaces distinctes, et vous désirez réaliser des opérations sur ces objets qui dépendent de leurs classes concrètes.

  • Il s’agit d’effectuer plusieurs opérations distinctes et sans relation entre elles, sur les objets d’une structure, et ceci en évitant de polluer leurs classes avec ces opérations.

  • Les classes qui définissent la structure objet changent rarement, mais on doit souvent définir de nouvelles opérations sur cette structure.

4.2. Le patron Visiteur

Visiteur (Visitor) permet la représentation d’une opération applicable aux éléments d’une structure d’objet.

Il définit une nouvelle opération, sans qu’il soit nécessaire de modifier la classe des éléments sur lesquels elle agit.

visitor dc
Figure 7. Patron Visiteur : structure
visitor seq
Figure 8. Patron Visiteur : comportement
visitor google
Figure 9. Visiteur sur Google

4.3. Avantages/Inconvénients

Avantages :

  • Permet d’ajouter des opérations à la structure d’un Composite sans modifier la structure elle-même.

  • L'ajout de nouvelles opérations est relativement facile.

  • Le code des opérations exécutées par le Visiteur est centralisé.

Inconvénients :

  • L’encapsulation des classes du Composite est brisée.

  • Comme une fonction de navigation est impliquée, les modifications de la structure du Composite sont plus difficiles.

4.4. Exemples d’utilisation

  • calcul sur un ensemble structuré d’éléments

  • génération de rapports ou de code

  • …​

4.5. Exemple concret d’utilisation en Java

Note Exemple tiré de ce site.
public interface ItemElement {

  public int accept(ShoppingCartVisitor visitor);
}
public class Book implements ItemElement {

  private int price;
  private String isbnNumber;

  public Book(int cost, String isbn){
    this.price=cost;
    this.isbnNumber=isbn;
  }

  public int getPrice() {
    return price;
  }

  public String getIsbnNumber() {
    return isbnNumber;
  }

  @Override
  public int accept(ShoppingCartVisitor visitor) {
    return visitor.visit(this);
  }

}
public class Fruit implements ItemElement {

  private int pricePerKg;
  private int weight;
  private String name;

  public Fruit(int priceKg, int wt, String nm){
    this.pricePerKg=priceKg;
    this.weight=wt;
    this.name = nm;
  }

  public int getPricePerKg() {
    return pricePerKg;
  }

  public int getWeight() {
    return weight;
  }

  public String getName(){
    return this.name;
  }

  @Override
  public int accept(ShoppingCartVisitor visitor) {
    return visitor.visit(this);
  }
}
public interface ShoppingCartVisitor {

  int visit(Book book);
  int visit(Fruit fruit);
}
public class ShoppingCartVisitorImpl implements ShoppingCartVisitor {

  @Override
  public int visit(Book book) {
    int cost=0;
    //apply 5$ discount if book price is greater than 50
    if(book.getPrice() > 50){
      cost = book.getPrice()-5;
    } else cost = book.getPrice();
    System.out.println("Book ISBN::"+book.getIsbnNumber() + " cost ="+cost);
    return cost;
  }

  @Override
  public int visit(Fruit fruit) {
    int cost = fruit.getPricePerKg()*fruit.getWeight();
    System.out.println(fruit.getName() + " cost = "+cost);
    return cost;
  }
}
public class ShoppingCartClient {

  public static void main(String[] args) {
    ItemElement[] items = new ItemElement[]{new Book(20, "1234"),new Book(100, "5678"),
    new Fruit(10, 2, "Banana"), new Fruit(5, 5, "Apple")};

    int total = calculatePrice(items);
    System.out.println("Total Cost = "+total);
  }

  private static int calculatePrice(ItemElement[] items) {
    ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
    int sum=0;
    for(ItemElement item : items){
      sum = sum + item.accept(visitor);
    }
    return sum;
  }

}
Book ISBN::1234 cost =20
Book ISBN::5678 cost =95
Banana cost = 20
Apple cost = 25
Total Cost = 160

5. Pour aller plus loin avec les patrons…​

plusloin

5.1. Partagez votre vocabulaire

  • Dans les réunions de conception (pas nécessairement avec le client)

  • Avec les autres développeurs

  • Dans la documentation de votre architecture

  • Dans les commentaires du code et les conventions de nommage

  • Dans les groupes/blogs de développeurs

  • (pas pendant les exams!)

5.2. Ne foncez pas tête baissée

Quelques conseils :

  • Les patterns sont des outils, non des règles.

    ⇒ Rien n’empêche de les modifier et de les adapter à votre problème.

  • Ne visez l’extensibilité que si la question se pose réellement dans la pratique, pas si elle est uniquement hypothétique.

  • Ne vous emballez pas et recherchez la simplicité.

    ⇒ Si vous trouvez une solution plus simple que l’emploi d’un pattern, n’hésitez pas !

  • Éliminez ce qui n’est pas vraiment nécessaire.

    ⇒ N’ayez pas peur de supprimer un design pattern inutile de votre conception.

5.3. Les autres types de patrons

Il n’y a pas que les 3 types de patrons que l’on a vu :

  • De création

  • Structurels

  • Comportementaux

Il y a par exemple :

  • Les patrons d’architecture

  • Les patrons d’application

  • Les patrons de domaine

  • Les patrons de processus

  • Les patrons d’organisation

  • Les patrons de conception d’interfaces utilisateur

5.4. Les anti-patrons

anti patrons
  • Des solutions souvent appliquées à tort à des problèmes récurrents

  • Décrit comment partir d’un problème pour arriver à une mauvaise solution

  • Vous dit pourquoi une mauvaise solution est attrayante

  • Suggère d’autres patrons applicables pouvant fournir de meilleures solutions

5.5. Tous les patrons qu’on a pas vu

Il y en a beaucoup :

  • Chaîne de responsabilité

  • Commande

  • Décorateur

  • Façade

  • Interprète

  • Médiateur

  • Mémento

  • Monteur

  • Patron de méthode

  • Poids-mouche

  • Pont

  • Prototype

5.6. Principale utilisation des patrons : refactoring!

⇒ Les 2 prochaines semaines vous allez refactorer une application :

  • en binôme

  • en testant

  • en documentant

  • en justifiant

  • en essayant pas à tout prix de caser le plus de patrons "au kilo"